import core.exception;

import std.conv;
import std.exception;
import std.string;

__gshared const string[string] ROTORS;
__gshared const string[string] REFLECTORS;

shared static this()
{
	ROTORS["I_wiring"] = "EKMFLGDQVZNTOWYHXUSPAIBRCJ";
	ROTORS["I_stepping"] = "Q";
	ROTORS["II_wiring"] = "AJDKSIRUXBLHWTMCQGZNPYFVOE";
	ROTORS["II_stepping"] = "E";
	ROTORS["III_wiring"] = "BDFHJLCPRTXVZNYEIWGAKMUSQO";
	ROTORS["III_stepping"] = "V";
	ROTORS["IV_wiring"] = "ESOVPZJAYQUIRHXLNFTGKDCMWB";
	ROTORS["IV_stepping"] = "J";
	ROTORS["V_wiring"] = "VZBRGITYUPSDNHLXAWMJQOFECK";
	ROTORS["V_stepping"] = "Z";
	ROTORS["VI_wiring"] = "JPGVOUMFYQBENHZRDKASXLICTW";
	ROTORS["VI_stepping"] = "ZM";
	ROTORS["VII_wiring"] = "NZJHGRCXMYSWBOUFAIVLPEKQDT";
	ROTORS["VII_stepping"] = "ZM";
	ROTORS["VIII_wiring"] = "FKQHTLXOCBJSPDZRAMEWNIUYGV";
	ROTORS["VIII_stepping"] = "ZM";
	REFLECTORS["B"] = "YRUHQSLDPXNGOKMIEBFZCWVJAT";
	REFLECTORS["C"] = "FVPJIAOYEDRZXWGCTKUQSBNMHL";
	REFLECTORS["B-Thin"] = "ENKQAUYWJICOPBLMDXZVFTHRGS";
	REFLECTORS["C-Thin"] = "RDOBJNTKVEHMLFCWZAXGYIPSUQ";
}

pure nothrow @nogc int modulo_26(int t)
{
	return (t + 25974) % 26; // Some evil stuff can be divided by 26
}

pure nothrow @nogc bool containsChar(string str, char needle)
{
	foreach (char ch; str)
	{
		if (ch == needle)
		{
			return true;
		}
	}

	return false;
}

struct Rotor
{
	int[26] entry_map;
	int[26] exit_map;
	int[26] display_map;
	int[26] pos_map;
	int pos = 0;
	int ring_setting;
	char display_val;
	string stepping;

	this(string wiring)
	{
		this(wiring, 0, "");
	}

	this(string wiring, int ring_setting, string stepping)
	{
		for (int i = 0; i < 26; i++)
		{
			this.entry_map[i] = cast(int)wiring[i] - cast(int)'A';
		}

		for (int i = 0; i < 26; i++)
		{
			this.exit_map[this.entry_map[i]] = i;
		}

		this.ring_setting = ring_setting;

		for (int i = 0; i < 26; i++)
		{
			this.display_map[i] = modulo_26(i - this.ring_setting);
			this.pos_map[modulo_26(i - this.ring_setting)] = i;
		}

		this.stepping = stepping;

		this.setDisplay('A');
	}

	public static Rotor createRotor(string model, int ring_setting)
	{
		auto wptr = (model ~ "_wiring") in ROTORS;
		auto sptr = (model ~ "_stepping") in ROTORS;

		if (wptr is null || sptr is null)
		{
			throw new Exception("Could not find rotor settings for " ~ model);
		}

		return Rotor(*wptr, ring_setting, *sptr);
	}

	public static Rotor createReflector(string model)
	{
		auto ptr = model in REFLECTORS;

		if (ptr is null)
		{
			throw new Exception("Could not find reflector settings for " ~ model);
		}

		return Rotor(*ptr);
	}

	public void setDisplay(char val)
	{
		this.pos = this.display_map[val - cast(int)'A'];
		this.display_val = val;
	}

	pure public int signal_in(int n)
	{
		return modulo_26(this.entry_map[(n + this.pos) % 26] - this.pos);
	}

	pure public int signal_out(int n)
	{
		return modulo_26(this.exit_map[(n + this.pos) % 26] - this.pos);
	}

	public bool notch_over_pawl()
	{
		return this.stepping.containsChar(this.display_val);
	}

	public void rotate()
	{
		this.pos = (this.pos + 1) % 26;
		this.display_val = cast(char)(cast(int)'A' + this.pos_map[this.pos]);
	}
}

class EnigmaMachine
{
	Rotor[3] rotors;
	Rotor reflector;

	private this(Rotor[] rotors, Rotor reflector)
	{
		this.rotors = rotors;
		this.reflector = reflector;
	}

	public static EnigmaMachine fromKeySheet(string list_rotors, string list_ring_settings, string reflector)
	{
		string[] _rotors = split(list_rotors);
		string[] _ring_settings = split(list_ring_settings);

		Rotor[3] rotor_list;

		for (int i = 0; i < 3; i++)
		{
			rotor_list[i] = Rotor.createRotor(_rotors[i], to!int(_ring_settings[i]));
		}

		return new EnigmaMachine(rotor_list, Rotor.createReflector(reflector));
	}

	public void setDisplay(inout char[] val)
	{
		for (int i = 2; i >= 0; --i)
		{
			if (cast(int)val[i] < 65 || cast(int)val[i] > 90)
			{
				return;
			}
			this.rotors[i].setDisplay(val[i]);
		}
	}

	private void _step_rotors()
	{
		this.rotors[2].rotate();
		if (this.rotors[2].notch_over_pawl() || this.rotors[1].notch_over_pawl())
		{
			this.rotors[1].rotate();
		}
		if (this.rotors[1].notch_over_pawl())
		{
			this.rotors[0].rotate();
		}
	}

	private int _electric_signal(int signal_num)
	{
		int pos = signal_num;

		for (int i = 2; i >= 0; --i)
		{
			pos = this.rotors[i].signal_in(pos);
		}

		pos = this.reflector.signal_in(pos);

		for (int i = 0; i < 3; i++)
		{
			pos = this.rotors[i].signal_out(pos);
		}

		return pos;
	}

	public string processText(string text)
	{
		char[] buf = new char[text.length];
		int i = 0;
		foreach (char ch; text)
		{
			int signal_num = cast(int)ch - cast(int)'A';

			if (signal_num < 0 || signal_num > 25) {
				throw new Exception("Non-uppercase input");
			}

			this._step_rotors();

			buf[i] = cast(char)(65 + this._electric_signal(signal_num));
			i++;
		}

		return assumeUnique(buf);
	}
}

unittest
{
	auto eni = EnigmaMachine.fromKeySheet("II VI V", "1 16 7", "B-Thin");
	eni.setDisplay(cast(char[])"WTF");
	assert(eni.processText("AJSOFDJISODHSFISJCSIPFDFSIPDUSIOPDJFSOIPIDOSDIOPRIEROPS") == "SXMRULHQEYYPVEKYHQQTHEORUOBKIHVEAOLXUSSEWJFWMLAGQRSEEVF");
	assert(eni.processText("DJFADFADFLADKFADSLASKDFLSD") == "TSRSTZNBSCBKAKMRBOJCOXTQTU");
}